package org.ponly.cta4j.drawing.background;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.SecureRandom;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.imageio.ImageIO;

/**
 * 图片背景笔刷.
 * <p>
 * 默认加载 classpath:META-INF/background/gimpy/ 下所有背景图. <br/>
 * 可以通过 locations 来指定背景图片文件夹, locations 支持 classpath: 路径. eg: "classpath:META-INF/background/, /opt/background".
 * </p>
 *
 * @author vacoor
 * @since 1.0
 */
public class ImageSourceBackgroundBrush extends AbstractBackgroundBrush {
    /**
     * jar url protocol.
     */
    public static final String JAR_PROTOCOL = "jar";

    /**
     * jar url protocol file separator.
     */
    public static final String JAR_SEPARATOR = "!/";

    /**
     * file url protocol.
     */
    public static final String FILE_PROTOCOL = "file";

    /**
     * classpath location prefix.
     */
    public static final String CLASSPATH_PREFIX = "classpath:";

    /**
     * default background locations.
     */
    public static final String DEFAULT_LOCATIONS = CLASSPATH_PREFIX + "META-INF/"
            + ImageSourceBackgroundBrush.class.getPackage().getName().replace('.', '/');

    private final Random random = new SecureRandom();
    private String locations;
    private List<BufferedImage> backgrounds;

    /**
     * 构建一个从默认位置({@link #DEFAULT_LOCATIONS})加载背景图的实例.
     */
    public ImageSourceBackgroundBrush() {
        this(DEFAULT_LOCATIONS);
    }

    /**
     * 构建一个从给定位置加载背景图的实例.
     *
     * @param locations 背景图位置
     */
    public ImageSourceBackgroundBrush(final String locations) {
        this.locations = locations;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doDrawBackground(final Graphics2D g2d, final int width, final int height) {
        final Image img = getBackground(width, height);
        if (null != img) {
            g2d.drawImage(img, 0, 0, width, height, null);
        }
    }

    /**
     * 获取给定宽度和高度的背景图片.
     *
     * @param width  画布宽度
     * @param height 画布高度
     * @return 背景图片
     */
    protected Image getBackground(final int width, final int height) {
        final List<BufferedImage> images = getBackgroundsLazy();
        final int size = images.size();
        return 0 < size ? images.get(random.nextInt(size)) : null;
    }

    /**
     * 延迟获取背景图片.
     *
     * @return 有效的背景图片
     */
    protected List<BufferedImage> getBackgroundsLazy() {
        if (null != backgrounds) {
            return backgrounds;
        }
        backgrounds = loadFromLocations(locations);
        return backgrounds;
    }

    /**
     * 获取当前的背景位置.
     *
     * @return 当前设置的背景位置
     */
    public String getLocations() {
        return locations;
    }

    /**
     * 设置新的背景位置.
     *
     * @param locations 新的背景位置
     */
    public void setLocations(final String locations) {
        this.locations = locations;
        this.backgrounds = null;
    }

    /**
     * 从给定的位置加载背景图片.
     *
     * @param locations 背景位置, 多个位置使用(" \t\n\r\f," 隔开)
     * @return 背景图片
     */
    protected List<BufferedImage> loadFromLocations(final String locations) {
        final List<BufferedImage> images = new ArrayList<>();
        final Checker checker = new Checker() {
            @Override
            public boolean value(final InputStream in) {
                final BufferedImage img = tryLoad(in);
                if (null != img) {
                    images.add(img);
                }
                return true;
            }
        };

        // iterate locations
        final StringTokenizer tokenizer = new StringTokenizer(locations, " \t\n\r\f,", false);
        while (tokenizer.hasMoreTokens()) {
            final String location = tokenizer.nextToken();

            // load from classpath
            if (location.startsWith(CLASSPATH_PREFIX)) {
                if (!loadFromClassPath(location.substring(CLASSPATH_PREFIX.length()), checker)) {
                    break;
                }
            } else {
                // load from file system directory
                if (!loadFromDir(new File(location), checker)) {
                    break;
                }
            }
        }

        return images;
    }

    /**
     * 从给定的 classpath 加载背景图片.
     *
     * @param path    classpath
     * @param checker 加载 Checker
     * @return 是否需要继续加载其他背景
     */
    protected boolean loadFromClassPath(final String path, final Checker checker) {
        final String classpath = path.startsWith("/") ? path.substring(1) : path;
        final ClassLoader loader = getClass().getClassLoader();

        try {
            final Enumeration<URL> resources = loader.getResources(classpath);
            while (resources.hasMoreElements()) {
                final URL resource = resources.nextElement();
                if (!loadFromResource(resource, checker)) {
                    return false;
                }
            }
        } catch (final Exception e) {
            // TODO
        }
        return true;
    }

    /**
     * 从给定的 URL 加载背景图片.
     *
     * @param resource url
     * @param checker  背景加载 checker
     * @return 是否需要继续加载其他背景
     */
    protected boolean loadFromResource(final URL resource, final Checker checker) {
        try {
            if (FILE_PROTOCOL.equals(resource.getProtocol())) {
                return loadFromDir(urlToFile(resource), checker);
            } else if (JAR_PROTOCOL.equals(resource.getProtocol())) {
                final String path = resource.getFile();
                final File jar = urlToFile(new URL(path.substring(0, path.indexOf(JAR_SEPARATOR))));
                String sub = path.substring(path.indexOf(JAR_PROTOCOL) + JAR_PROTOCOL.length());

                sub = URLDecoder.decode(sub, "UTF-8").replace('+', ' ');
                return loadFromJar(jar, sub, checker);
            }
        } catch (final Exception e) {
            // TODO
            // Cann't load {}: {}, resource, e
        }
        return true;
    }

    /**
     * 从给定的目录/文件中加载背景图片.
     *
     * @param file    背景图片或背景目录
     * @param checker 背景加载 Checker
     * @return 是否需要继续加载其他背景
     */
    protected boolean loadFromDir(final File file, final Checker checker) {
        if (file.exists()) {
            if (file.isFile()) {
                try {
                    return checker.value(new FileInputStream(file));
                } catch (final FileNotFoundException e) {
                    // TODO
                }
            } else if (file.isDirectory()) {
                File[] files = file.listFiles();
                files = null != files ? files : new File[0];
                for (final File f : files) {
                    if (!loadFromDir(f, checker)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 从给定的 jar 文件中加载背景图片.
     *
     * @param file    背景图片或背景目录
     * @param path    背景图片相对jar文件的路径
     * @param checker 背景加载 Checker
     * @return 是否需要继续加载其他背景
     */
    protected boolean loadFromJar(final File file, final String path, final Checker checker) {
        try {
            final String prefix = path.endsWith("/") ? path : path + '/';
            final ZipFile zipFile = new ZipFile(file);
            try {
                final Enumeration<? extends ZipEntry> entries = zipFile.entries();
                while (entries.hasMoreElements()) {
                    final ZipEntry entry = entries.nextElement();
                    final String name = entry.getName();

                    if (entry.isDirectory() || !name.startsWith(prefix)) {
                        continue;
                    }

                    if (!checker.value(zipFile.getInputStream(entry))) {
                        return false;
                    }
                }
            } catch (final Throwable e) {
                // TODO
                // LOG.warn("File '{}' contains invalid plugin descriptor.", file.getName(), e);
            } finally {
                zipFile.close();
            }
        } catch (final Throwable e) {
            // TODO
            // LOG.error("Cannot load '{}'", file, e);
        }
        return true;
    }

    /**
     * 转换 URL 到 File.
     *
     * @param url URL
     * @return url 对应的文件
     * @throws URISyntaxException    URISyntaxException
     * @throws MalformedURLException MalformedURLException
     */
    // work around corrupted URLs produced by File.getURL()
    protected File urlToFile(final URL url) throws URISyntaxException, MalformedURLException {
        try {
            return new File(url.toURI());
        } catch (final URISyntaxException e) {
            final String str = url.toString();
            if (str.indexOf(' ') > 0) {
                return new File(new URL(str.replace(" ", "%20")).toURI());
            }
            throw e;
        }
    }

    /**
     * 尝试加载图片, 如果加载失败返回 null.
     *
     * @param in 图片输入流
     * @return 图片或null
     */
    protected BufferedImage tryLoad(final InputStream in) {
        try {
            return ImageIO.read(in);
        } catch (final IOException e) {
            return null;
        } finally {
            try {
                in.close();
            } catch (final IOException e) {
                // ignore
            }
        }
    }

    /**
     * 图片加载 checker.
     */
    public interface Checker {

        /**
         * 处理给定图片流, 返回是否需要进一步加载.
         *
         * @param in 图片输入流
         * @return 是否需要继续加载
         */
        boolean value(InputStream in);

    }
}
